/// Copyright (c) Microsoft Corporation.  All rights reserved.
using System;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace TfsTasks
{
    public class UpdateBuddyBuildWorkItem : Task
    {
        #region Constants
        private const string STATE_RESOLVED = "Resolved";
        private const string RESOLUTION_APPROVED = "Approved";
        private const string RESOLUTION_REJECTED = "Rejected";
        private const string WORKITEM_RESOLUTION_FIELDNAME = "Resolution";
        private const string RESULT_SUCCESS = "success";
        private const string RESULT_FAILURE = "failure";
        private const string MESSAGE_WORKITEM_UPDATED_SUCCESSFULLY = "Successfully updated work item #{0}.";
        private const string MESSAGE_WORKITEM_SET_REJECTED = "Setting work item #{0} to 'Rejected'.";
        private const string MESSAGE_WORKITEM_SET_APPROVED = "Setting work item #{0} to 'Approved'.";
        private const string MESSAGE_WORKITEM_NOT_FOUND = "Could not find work item #{0}";
        private const string MESSAGE_INVALID_RESULT = "BuildResult argument can be either 'success' or 'failure'";
        private const int THREAD_SLEEP_DURATION_MILLISECONDS = 500;
        private const string MESSAGE_RETRYING_WORKITEM_UPDATE = "Re-trying to update work item.";
        private const string MESSAGE_FAILED_TWICE_UPDATING_WORKITEM = "Failed twice while updating work item.";
        #endregion

        #region Private Fields
        private int _workItemId;
        private string _buildResult;
        private string _teamFoundationServerUrl;
        #endregion

        #region Public Properties
        // The [Required] attribute indicates a required property.
        // If a project file invokes this task without passing a value
        // to this property, the build will fail immediately.
        [Required]
        public string TeamFoundationServerUrl
        {
            get
            {
                return _teamFoundationServerUrl;
            }
            set
            {
                _teamFoundationServerUrl = value;
            }
        }

        [Required]
        public int WorkItemId
        {
            get
            {
                return _workItemId;
            }
            set
            {
                _workItemId = value;
            }
        }

        [Required]
        public string BuildResult
        {
            get
            {
                return _buildResult;
            }
            set
            {
                _buildResult = value;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Execute the task. Update a work item's resolution to either "Approved"
        /// or "Rejected".
        /// </summary>
        /// <returns>true</returns>
        /// <remarks>
        /// This method will always return true since we do not want to fail a build
        /// if the work item update failed. We still log the error message to the
        /// build log but let the task (and possibly the build) succeed.
        /// 
        /// Due to the fact that sometimes there are some unexpected TFS outages,
        /// the work item update is attempted a second time if it fails the first time.
        /// </remarks>
        public override bool Execute()
        {
            bool result = this.ExecuteInternal();
            if (result) return result;
            Log.LogMessage(MessageImportance.High, MESSAGE_RETRYING_WORKITEM_UPDATE);
            System.Threading.Thread.Sleep(THREAD_SLEEP_DURATION_MILLISECONDS); // small wait.
            result = this.ExecuteInternal();
            if (!result) Log.LogMessage(MessageImportance.High, MESSAGE_FAILED_TWICE_UPDATING_WORKITEM);
            return true; // always return true so we don't fail build due to work item issues.
        }
        #endregion

        #region Private Methods
        private bool ExecuteInternal()
        {
            // Update the work item based on the result ("success" or "failure").
            string buildResult = (_buildResult == null) ? string.Empty : _buildResult.ToLower();

            if (buildResult != RESULT_SUCCESS && buildResult != RESULT_FAILURE)
            {
                Log.LogError(MESSAGE_INVALID_RESULT);
                return false;
            }

            try
            {
                using (TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(_teamFoundationServerUrl))
                {
                    WorkItemStore store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
                    WorkItem workItem = store.GetWorkItem(_workItemId);
                    if (workItem == null)
                    {
                        Log.LogError(MESSAGE_WORKITEM_NOT_FOUND, _workItemId);
                        return false;
                    }

                    workItem.State = STATE_RESOLVED;

                    if (buildResult == RESULT_SUCCESS)
                    {
                        Log.LogMessage(MessageImportance.High, MESSAGE_WORKITEM_SET_APPROVED, _workItemId);
                        workItem.Fields[WORKITEM_RESOLUTION_FIELDNAME].Value = RESOLUTION_APPROVED;
                    }
                    else
                    {
                        Log.LogMessage(MessageImportance.High, MESSAGE_WORKITEM_SET_REJECTED, _workItemId);
                        workItem.Fields[WORKITEM_RESOLUTION_FIELDNAME].Value = RESOLUTION_REJECTED;
                    }

                    workItem.Save();
                }
            }
            catch (Exception ex)
            {
                Log.LogErrorFromException(ex, true);
                return false;
            }

            Log.LogMessage(MessageImportance.High, MESSAGE_WORKITEM_UPDATED_SUCCESSFULLY, _workItemId);
            return true;
        }
        #endregion
    }
}
